package com.zhoug.map.amap;

import android.content.Context;
import android.util.Log;

import com.amap.api.maps.AMap;
import com.amap.api.maps.CameraUpdateFactory;
import com.amap.api.maps.Projection;
import com.amap.api.maps.model.AMapPara;
import com.amap.api.maps.model.BaseHoleOptions;
import com.amap.api.maps.model.CameraPosition;
import com.amap.api.maps.model.LatLng;
import com.amap.api.maps.model.LatLngBounds;
import com.amap.api.maps.model.Polygon;
import com.amap.api.maps.model.PolygonHoleOptions;
import com.amap.api.maps.model.PolygonOptions;
import com.amap.api.maps.model.VisibleRegion;
import com.amap.api.services.core.AMapException;
import com.amap.api.services.core.LatLonPoint;
import com.amap.api.services.district.DistrictItem;
import com.amap.api.services.district.DistrictResult;
import com.amap.api.services.district.DistrictSearch;
import com.amap.api.services.district.DistrictSearchQuery;
import com.zhoug.map.MapUtils;
import com.zhoug.map.data.database.MapDatabase;
import com.zhoug.map.data.database.dao.BoundaryDao;
import com.zhoug.map.data.database.dao.CityDao;
import com.zhoug.map.data.database.entities.Boundary;
import com.zhoug.map.data.database.entities.City;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.OnLifecycleEvent;
import io.reactivex.Observable;
import io.reactivex.ObservableOnSubscribe;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;

/**
 * 用空心多边形显示单个省份地图
 *
 * @Author 35574
 * @Date 2021/9/16
 * @Description
 */
public class MapProvinceShowLimitHelper implements DistrictSearch.OnDistrictSearchListener, LifecycleObserver {
    private static final String TAG = ">>>MapProvinceShowLimit";
    private static boolean debug = false;
    private boolean destroy = false;
    private ThreadPoolExecutor mThreadPoolExecutor;
    private OnListener onListener;

    private Context context;
    private AMap aMap;
    /**
     * 需要显示地图的省市名称/区域代码
     * 最小支持到区县级边界,不能查询全国的,因为数据太多会卡死
     */
    private String city;
    /**
     * 显示的地图距离边界的距离
     */
    private int boundPadding = 100;
    /**
     * 地图遮罩颜色
     */
    private int maskColor = 0xfff3f3f3;
    /**
     * 边界线宽度
     */
    private float strokeWidth = 2;
    /**
     * 边界线颜色
     */
    private int strokeColor = 0xffeeeeee;

    /**
     * 设置多边形的Z轴数值
     */
    private int zIndex = 10;

    /**
     * 每条边界的点数限制,加载太多会卡顿
     */
    private int boundLatLngCountLimit = 1000;

    private Polygon polygon;

    /**
     * 地图边界用于清除上次的地图限制
     */
    private LatLngBounds mapBounds;
    /**
     * 边界坐标
     */
    private double top;
    private double bottom;
    private double left;
    private double right;
    /**
     * 边界
     */
    private LatLngBounds latLngBounds;
    private boolean byParent;//如果设置的区域没有查询到边界则查询上级区域,直到查询成功

    public MapProvinceShowLimitHelper(LifecycleOwner lifecycleOwner, @NonNull Context context, @NonNull AMap aMap) {
        this.context = context;
        this.aMap = aMap;
        if (lifecycleOwner != null) {
            lifecycleOwner.getLifecycle().addObserver(this);
        }
        mThreadPoolExecutor = new ThreadPoolExecutor(3,
                10,
                60, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(100000)
        );
        mThreadPoolExecutor.allowCoreThreadTimeOut(true);
        mapBounds = new LatLngBounds(new LatLng(-84.9, -179.9), new LatLng(84.9, 179.9));
    }

    /**
     * 设置显示的省市名称/区域代码
     *
     * @param city
     * @return
     */
    public MapProvinceShowLimitHelper setCity(String city) {
        this.city = city;
        return this;
    }

    /**
     * 显示的地图距离边界的距离
     *
     * @param boundPadding
     * @return
     */
    public MapProvinceShowLimitHelper setBoundPadding(int boundPadding) {
        this.boundPadding = boundPadding;
        return this;
    }

    /**
     * 地图遮罩颜色
     *
     * @param maskColor
     * @return
     */
    public MapProvinceShowLimitHelper setMaskColor(int maskColor) {
        this.maskColor = maskColor;
        return this;
    }

    /**
     * 边界线宽度
     *
     * @param strokeWidth
     * @return
     */
    public MapProvinceShowLimitHelper setStrokeWidth(float strokeWidth) {
        this.strokeWidth = strokeWidth;
        return this;
    }

    /**
     * 边界线颜色
     *
     * @param strokeColor
     * @return
     */
    public MapProvinceShowLimitHelper setStrokeColor(int strokeColor) {
        this.strokeColor = strokeColor;
        return this;
    }

    /**
     * 设置多边形的Z轴数值
     *
     * @param zIndex
     * @return
     */
    public MapProvinceShowLimitHelper setZIndex(int zIndex) {
        this.zIndex = zIndex;
        return this;
    }

    /**
     * 每条边界的点数限制,加载太多会卡顿
     *
     * @param boundLatLngCountLimit
     * @return
     */
    public MapProvinceShowLimitHelper setBoundLatLngCountLimit(int boundLatLngCountLimit) {
        this.boundLatLngCountLimit = boundLatLngCountLimit;
        return this;
    }

    /**
     * 边界经纬度
     *
     * @return
     */
    public @Nullable
    LatLngBounds getLatLngBounds() {
        return latLngBounds;
    }

    /**
     * 清除数据
     */
    public void clear() {
        if (polygon != null) {
            polygon.remove();
            polygon = null;
        }
        left = 0;
        right = 0;
        top = 0;
        bottom = 0;
        latLngBounds = null;
        if (aMap != null) {
            AMapHelper.setMapLimits(aMap, mapBounds.southwest, mapBounds.northeast);
        }
    }

    private Disposable disposable;


    private void dispose() {
        if (disposable != null && !disposable.isDisposed()) {
            disposable.dispose();
            disposable = null;
        }
    }

    /**
     * 只显示指定省市的地图
     */
    public void show() {
        dispose();
        disposable = Observable
                .create((ObservableOnSubscribe<Boolean>) emitter -> {
                    emitter.onNext(searchDistrictLocal());
                    emitter.onComplete();
                })
                .subscribeOn(Schedulers.io())
                .observeOn(Schedulers.io())
                .subscribe(hasLocal -> {
                    if (!hasLocal) {
                        searchDistrictRemote();
                    }
                }, throwable -> {
                    throwable.printStackTrace();
                    searchDistrictRemote();
                });


    }

    /**
     * 调用接口获取边界
     *
     * @return
     */
    private void searchDistrictRemote() {
        if (debug) {
            Log.d(TAG, "调用接口获取边界");
        }
        DistrictSearch districtSearch = new DistrictSearch(context);
        DistrictSearchQuery query = new DistrictSearchQuery();
        query.setKeywords(city);
        query.setShowBoundary(true);
        query.setSubDistrict(0);//获取边界不需要返回下级行政区
        districtSearch.setQuery(query);
        districtSearch.setOnDistrictSearchListener(this);
        //请求边界数据 开始展示
        districtSearch.searchDistrictAsyn();
    }

    /**
     * 从本地数据库中查询边界
     */
    private boolean searchDistrictLocal() {
        CityDao cityDao = MapDatabase.getInstance().getCityDao();
        City c = cityDao.findByAreaCode(city);
        if (c == null) {
            c = cityDao.findByName(city);
        }
        if (c != null) {
            BoundaryDao boundaryDao = MapDatabase.getInstance().getBoundaryDao();
            List<Boundary> boundaries = boundaryDao.findByAreaCode(c.getAreaCode());
            if (boundaries != null && boundaries.size() > 0) {
                if (debug) {
                    Log.d(TAG, "从本地数据库中查询边界成功");
                }
                String[] bounds = new String[boundaries.size()];
                for (int i = 0; i < boundaries.size(); i++) {
                    bounds[i] = boundaries.get(i).getBound();
                }
                showCityBounds(bounds);
                return true;
            }
        }
        if (debug) {
            Log.d(TAG, "本地数据库没有存储边界:" + city);
        }
        return false;
    }

    @Override
    public void onDistrictSearched(DistrictResult districtResult) {
        if (destroy) {
            Log.e(TAG, "destroy");
            return;
        }
        if (districtResult == null) {
            Log.e(TAG, "districtResult is null ");
            if (onListener != null) {
                onListener.onFailure("没有获取到地图区域数据");
            }
            return;
        }
        if (districtResult.getAMapException() != null && districtResult.getAMapException().getErrorCode() == AMapException.CODE_AMAP_SUCCESS) {
            boolean success = false;
            ArrayList<DistrictItem> district = districtResult.getDistrict();
            if (district != null && district.size() > 0) {
                if (debug) {
                    Log.d(TAG, "district.size=" + district.size());
                }
                final DistrictItem districtItem = district.get(0);
                if (debug) {
                    Log.d(TAG, "districtItem=" + districtItem);
                }
                if (districtItem != null) {
                    final String[] bounds = districtItem.districtBoundary();
                    if (bounds == null || bounds.length == 0) {
                        Log.e(TAG, "bounds is empty");
                    } else {
                        success = true;
                        if (debug) {
                            Log.d(TAG, "bounds length:" + bounds.length);
                        }
                        //显示边界
                        mThreadPoolExecutor.execute(() -> showCityBounds(bounds));
                        //保存
                        mThreadPoolExecutor.execute(() -> keepCity(districtItem));
                    }
                } else {
                    Log.e(TAG, "districtItem is null");
                }

            } else {
                Log.e(TAG, "没有查询到区域");
            }
            if (!success) {
                if (byParent && MapUtils.isNumber(city)) {
                    String parentAreaCode = MapUtils.getParentAreaCode(city);
                    if (parentAreaCode != null) {
                        Log.d(TAG, "查询上级区域:" + parentAreaCode);
                        city = parentAreaCode;
                        show();
                    } else {
                        if (onListener != null) {
                            onListener.onFailure("没有获取到地图区域数据");
                        }
                    }
                } else {
                    if (onListener != null) {
                        onListener.onFailure("没有获取到地图区域数据");
                    }
                }
            }


        } else {
            Log.e(TAG, "发生错误");
            if (onListener != null) {
                onListener.onFailure("没有获取到地图区域数据");
            }
        }


    }

    /**
     * 把城市边界保存到数据库
     *
     * @param districtItem
     */
    private void keepCity(DistrictItem districtItem) {
        if (debug) {
            Log.d(TAG, "把城市边界保存到数据库");
        }
        //城市
        try {
            City city = new City();
            String adcode = districtItem.getAdcode();
            city.setAreaCode(adcode);
            city.setName(districtItem.getName());
            city.setLevel(districtItem.getLevel());
            LatLonPoint center = districtItem.getCenter();
            if (center != null) {
                city.setLatitude(center.getLatitude());
                city.setLongitude(center.getLongitude());
            }
            //城市边界
            final String[] bounds = districtItem.districtBoundary();
            List<Boundary> boundaries = new ArrayList<>();
            for (String bound : bounds) {
                boundaries.add(new Boundary(adcode, bound));
            }
            MapDatabase.getInstance().getCityDao().insert(city);
            MapDatabase.getInstance().getBoundaryDao().insertAll(boundaries);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * 使用矩形空洞显示地图
     *
     * @param bounds
     */
    private void showCityBounds(String[] bounds) {
        if (debug) {
            Log.d(TAG, "使用矩形空洞显示地图");
        }
        long startTime = System.currentTimeMillis();
        //清除上次绘制
        clear();
        List<LatLng> latLngs = new ArrayList<>();
        //绘制一个全世界的多边形,遮住地图
        latLngs.add(new LatLng(84.9, -179.9));
        latLngs.add(new LatLng(84.9, 179.9));
        latLngs.add(new LatLng(-84.9, 179.9));
        latLngs.add(new LatLng(-84.9, -179.9));
        polygon = aMap.addPolygon(new PolygonOptions().addAll(latLngs)
                .fillColor(maskColor)
                .strokeColor(strokeColor)
                .strokeWidth(strokeWidth)
                .lineJoinType(AMapPara.LineJoinType.LineJoinRound)
                .zIndex(zIndex));

        List<BaseHoleOptions> holeOptionsList = new ArrayList<>();
        for (String bound : bounds) {
            if (debug) {
                Log.d(TAG, "bound:" + bound);
            }
            //bound: 106.076469,32.759056;106.076649,32.759653;....
            String[] lat = bound.split(";");
            if (lat.length > 0) {
                boolean isFirst = true;
                //每个边界形成的多边形洞
                PolygonHoleOptions polygonHoleOptions = new PolygonHoleOptions();
                //存储每条边界线的经纬度
                List<LatLng> holeLatLngs = new ArrayList<>();
                LatLng firstLatLng = null;
                LatLng latLng = null;
                int length = lat.length;
                int offset = length / boundLatLngCountLimit;//最多取2000个点
                if (debug) {
                    Log.d(TAG, "lat.length=" + length + ",offset=" + offset);
                }
                for (int i = 0; i < length; i++) {
                    String latstr = lat[i];
                    if (offset > 0 && i % offset != 0) {
                        continue;
                    }
                    //106.076469,32.759056
                    String[] lats = latstr.split(",");
                    if (isFirst) {
                        isFirst = false;
                        firstLatLng = new LatLng(Double
                                .parseDouble(lats[1]), Double
                                .parseDouble(lats[0]));
                    }
                    latLng = new LatLng(Double
                            .parseDouble(lats[1]), Double
                            .parseDouble(lats[0]));
                    holeLatLngs.add(latLng);
                    compareBound(latLng);
                }
                holeLatLngs.add(firstLatLng);

                if (holeLatLngs.size() > 0) {
                    polygonHoleOptions.addAll(holeLatLngs);
                    holeOptionsList.add(polygonHoleOptions);
                }
            }
        }

        latLngBounds = LatLngBounds.builder()
                .include(new LatLng(bottom, left))
                .include(new LatLng(top, right))
                .build();
        if (debug) {
            Log.d(TAG, "showProvince:bottom=" + bottom);
            Log.d(TAG, "showProvince:top=" + top);
            Log.d(TAG, "showProvince:left=" + left);
            Log.d(TAG, "showProvince:right=" + right);
        }

        //把全部边界显示在地图上
        aMap.moveCamera(CameraUpdateFactory.newLatLngBounds(latLngBounds, boundPadding));
        //限制地图显示范围
        aMap.addOnCameraChangeListener(onCameraChangeListener);

        if (holeOptionsList.size() > 0) {
            ListComparator c = new ListComparator();
            // 将洞的内容排序，优先显示量大的洞，避免出现洞相互叠加，导致无法展示
            Collections.sort(holeOptionsList, c);
            int allSize = 0;
            for (BaseHoleOptions holeOptions : holeOptionsList) {
                PolygonHoleOptions polygonHoleOption = (PolygonHoleOptions) holeOptions;
                int size = polygonHoleOption.getPoints().size();
                if (debug) {
                    Log.d(TAG, "polygonHoleOption size :" + size);
                }
                allSize += size;
            }

            if (debug) {
                Log.d(TAG, "allSize=" + allSize);
            }
            polygon.setHoleOptions(holeOptionsList);

        }
        long endTime = System.currentTimeMillis();
        if (debug) {
            Log.d(TAG, "耗时:" + (endTime - startTime));
        }
    }

    /**
     * 计算边界矩形坐标
     *
     * @param latLng
     */
    private void compareBound(@NonNull LatLng latLng) {
        if (top == 0 && bottom == 0 && left == 0 && right == 0) {
            top = latLng.latitude;
            bottom = latLng.latitude;
            left = latLng.longitude;
            right = latLng.longitude;
        } else {
            if (top < latLng.latitude) {
                top = latLng.latitude;
            }
            if (bottom > latLng.latitude) {
                bottom = latLng.latitude;
            }
            if (left > latLng.longitude) {
                left = latLng.longitude;
            }
            if (right < latLng.longitude) {
                right = latLng.longitude;
            }
        }
    }

    /**
     * 限制地图显示范围
     */
    private AMap.OnCameraChangeListener onCameraChangeListener = new AMap.OnCameraChangeListener() {
        @Override
        public void onCameraChange(CameraPosition cameraPosition) {
            Log.d(TAG, "onCameraChange:");
        }

        @Override
        public void onCameraChangeFinish(CameraPosition cameraPosition) {
            Log.d(TAG, "onCameraChangeFinish:");
            //限制地图显示
            if (aMap != null) {
                aMap.removeOnCameraChangeListener(onCameraChangeListener);
                Projection projection = aMap.getProjection();
                if (projection != null) {
                    VisibleRegion visibleRegion = projection.getVisibleRegion();
                    if (visibleRegion != null && visibleRegion.latLngBounds != null) {
                        AMapHelper.setMapLimits(aMap, visibleRegion.latLngBounds.southwest, visibleRegion.latLngBounds.northeast);
                        if (onListener != null) {
                            onListener.onLimitMapFinish();
                        }
                    }
                }
            }

        }
    };


    static class ListComparator implements Comparator<Object>,
            Serializable {
        /**
         *
         */
        private static final long serialVersionUID = 1L;

        @Override
        public int compare(Object lhs, Object rhs) {
            PolygonHoleOptions fir = (PolygonHoleOptions) lhs;
            PolygonHoleOptions sec = (PolygonHoleOptions) rhs;
            try {
                if (fir != null && sec != null && fir.getPoints() != null) {
                    if (fir.getPoints().size() < sec.getPoints().size()) {
                        return 1;
                    } else if (fir.getPoints().size() > sec.getPoints().size()) {
                        return -1;
                    }
                }
            } catch (Throwable ex) {
                ex.printStackTrace();
            }
            return 0;
        }
    }

    public static void sortArray(String[] arr) {
        if (arr == null || arr.length == 0) return;
        int length = arr.length - 1;
        String temp;
        for (int i = 0; i < length; i++) {
            for (int j = 0; j < length - i; j++) {
                if (arr[j].length() < arr[j + 1].length()) {
                    temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                }
            }
        }

    }

    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    public void onDestroy(@NonNull LifecycleOwner owner) {
        destroy = true;
        dispose();
        if (debug) {
            Log.d(TAG, "onDestroy");
        }
    }

    public void setByParent(boolean byParent) {
        this.byParent = byParent;
    }

    public void setOnListener(OnListener onListener) {
        this.onListener = onListener;
    }

    public interface OnListener {
        /**
         * 限制地图显示区域设置完成后回掉
         */
        void onLimitMapFinish();

        /**
         * 显示区域地图失败
         *
         * @param error 失败,没有获取到区域边界,发生异常等错误信息
         */
        void onFailure(String error);

    }
}
